/**
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Client
 *	\author
 *	\file client/main.c:
 *	\brief Hauptprogramm des Clients */

/* ** ** ** Includes ** ** ** */

#include "common/sockets.h"
#include "main.h"
#include "listener.h"
#include "fragewechsel.h"
#include "command.h"

/* ** ** ** Client-ID Daten ** ** ** */

uint8_t client_id;	/**< Globale ClientId */

/** \brief ClientId holen
 * 	\return ClientId zurückgeben */
int get_client_id() {
	return client_id;
}

/* ** ** ** Client-ID Daten ** ** ** */

int sock;	/**<  Globale Variable für Server Socket */

/** \brief Socket holen
 *  \return SocketId zurückgeben */
int get_socket() {
	return sock;
}

/* ** ** ** Main Arg Daten ** ** ** */

int argc_main;
char **argv_main;

/** \brief Main Daten holen
 *  \return argc_main */
int get_main_argc() {
	return argc_main;
}
/** \brief Main Daten holen
 *  \return argv_main */
char** get_main_argv() {
	return argv_main;
}

/* ** ** ** Thread-Daten ** ** ** */

pthread_t thread_command;	/**< Globale ThreadId  */

/** \brief Command-ThreadId holen
 *  \return ThreadId zurückgeben */
pthread_t get_command_thread() {
	return thread_command;
}

/** \brief Login-Routine
 * 	Sendet Login-Header und
 * 	Login-Request an Server und wartet
 * 	auf Login-Response-OK
 * 	\return Login erfolgreich oder nicht
 *	\param name Beinhaltet den Spielernamen */
uint8_t login(char *name) {

	/* ** ** ** Sendet Login-Anfrage an Server ** ** ** */

	if (catch_com_err(send_MSG(sock, LOGIN_REQUEST, strlen(name), name)) == COM_ERR) {
		return 255;
	}

	/* ** ** ** Daten für Login-OK ** ** ** */

	// MSG-Struktur für Nachrichtenempfang
	struct RFC_MSG login_response;

	// Daten für Error-Behandlung
	uint8_t subtype = 0;
	char* err_msg;

	/* ** ** ** Empfange Login-OK ** ** ** */

	switch (catch_com_err(receive_MSG(sock, &login_response))) {

	/* ** ** ** Login-OK erhalten ** ** ** */

	case LOGIN_RESPONSE_OK:

		// Kopiere Daten des Void-Pointer in Zahl
		memcpy(&client_id, login_response.data, login_response.header.length);

		// Freigeben des dynamischen Speichers (sockets. c - receive_MSG())
		free(login_response.data);

		// Rückkehr mit Client-ID
		return (client_id);

		/* ** ** ** Error empfangen ** ** ** */

	case ERROR_WARNING:

		// Hole Speicher für Error-Msg
		err_msg = (char*) malloc(login_response.header.length + 1);

		// err_msg mit Nullen füllen (automatische Ende-Markierung)
		memset(err_msg, 0, login_response.header.length + 1);

		// Kopiere Subtyp und Fehlerbeschreibung aus Error-MSG
		memcpy(&subtype, login_response.data, sizeof(uint8_t));
		memcpy(err_msg, login_response.data + sizeof(uint8_t), login_response.header.length - sizeof(uint8_t));

		// Unterscheide Error-Typ
		switch (subtype) {

		// Wenn Error-Typ Warnung
		case WARNING:
			infoPrint("%s\n", err_msg);

			// Freigeben des Speichers
			free(err_msg);
			err_msg = NULL;
			break;

			// Wenn Error-Typ Fatal
		case FATAL:
			infoPrint("%s\n", err_msg);
			return (255);

		default:
			infoPrint("Unbekannter Fehler ist im Server aufgetreten\n");
			break;
		}
		break;

	case COM_ERR:
		return (255);

	default:
		break;
	}

	// Bei fehlgeschlagenem Login
	return (255);
}

/** \brief Verarbeitung der Startparameter
 *  Startparameter beim Aufruf über die Konsole werden
 *  hier verarbeitet
 *  \param args Beinhaltet Konsolenparameter */
void parse_commands(char** args) {
	// opt = gerade geladene Option, option_count = zähle wichtige Optionen
	int opt, option_count = 0;

	// Solange Optionsindex kleiner als Anzahl der Optionen
	while (optind < argc_main) {

		// Hole Option an Stelle optin (nur n, h und p), optind wird um eins erhöht
		opt = getopt(argc_main, argv_main, "n:h:p:d");

		// Keine Argumente mehr vorhanden
		if (opt == -1) {
			break;
		}

		switch (opt) {

		// Wenn kein Argument für die Option angegeben wurde
		case ':':
			infoPrint("Client beendet - fehlende Argumente...\n");
			infoPrint("client -n NAME -h SERVER -p PORT [-d (DebugMode)]\n");
			infoPrint("Beispiel: ./client -n Spieler -h 127.0.0.1 -p 54321\n");
			exit(0);
			break;

			// Option name
		case 'n':
			// Argument von Option n in args[0]
			args[0] = optarg;
			// wenn Name länger als 31 Zeichen, breche mit Hinweis auf maximale Länge ab
			if (strlen(args[0]) > 31) {
				infoPrint("Eingegebener Name zu lang - maximal 31 Zeichen erlaubt\n");
				infoPrint("Beende...\n");
				exit(0);
			}
			option_count++;
			break;

			// Option host
		case 'h':
			// Argument von Option h in args[1]
			args[1] = optarg;
			option_count++;
			break;

			// Option port
		case 'p':
			// Argument von Option p in args[2]
			args[2] = optarg;
			option_count++;
			break;

			// Option debugMode
		case 'd':
			// Debug Modus an
			debugEnable();
			break;

			// Option nicht bekannt
		case '?':
			infoPrint("Option nicht bekannt\n");
			break;

		default:
			break;
		}
	}

	// Wenn weniger als 3 Optionen eingegeben wurde
	if (option_count < 3) {
		infoPrint("Client beendet - fehlende Argumente...\n");
		infoPrint("client -n NAME -h SERVER -p PORT\n");
		infoPrint("Beispiel: ./client -n Spieler -h 127.0.0.1 -p 54321\n");
		exit(0);
	}

	return;
}

/** \brief Start des Client
 * 	Hauptprogramm des Clients
 * 	Verbindung zum Server über socket aufbauen
 * 	Login Versuch Starten
 * 	Threads Starten
 * 	\param argv Beinhaltet Programmname */
int main(int argc, char **argv) {
	setProgName(argv[0]);

	/* ** ** ** Start Parameter einlesen ** ** ** */

	// Parameter in Globalen Variablen ablegen
	argc_main = argc;
	argv_main = argv;

	// Hier werden die Argumente gespeichert
	char* args[3];

	parse_commands(args);

	// Ausgabe Client-Version
	infoPrint("Client Gruppe 05\n");

	/* ** ** ** Versuche Socket Verbindung zu Server aufzubauen ** ** ** */

	// Verbindung zu Server aufbauen über Socket "sock"
	sock = create_client_socket(args);

	// Fange Fehler bei der Socket-Erstellung und Verbindungsaufbau ab
	if (sock == -1) {
		perror("Verbindung konnte nicht aufgebaut werden: ");
		exit(0);
	}

	/* ** ** ** Versuche Login ** ** ** */

	// Login-Prozedur bei erfolgreich verbundenem Socket
	client_id = login(args[0]);

	// Fange Fehler beim Login ab
	if (client_id == 255) {
		infoPrint("Login gescheitert... Beende...\n");
		exit(0);
	}

	/* ** ** ** Login OK - starte Threads ** ** ** */

	// Semaphor für Command und Listener-Thread, damit Listener-Thread keine Funktionen der GUI aufrufen kann, bevor diese Initialisiert wurde
	sem_init(&command_init, 0, 0);

	// Command-Thread erzeugen und Fehler beim erzeugen abfangen
	if (pthread_create(&thread_command, NULL, command_thread, NULL) != 0) {
		debugPrint("Command-Thread Error\n");
		return 0;
	}

	pthread_t thread_listener;
	// Listener-Thread erzeugen
	if (pthread_create(&thread_listener, NULL, listener_thread, NULL) != 0) {
		debugPrint("Listener-Thread Error\n");
		return 0;
	}

	pthread_t thread_question;
	// Fragewechsel-Thread erzeugen
	if (pthread_create(&thread_question, NULL, question_thread, NULL) != 0) {
		debugPrint("Fragewechsel-Thread Error");
		return 0;
	}

	/* ** ** ** Warte auf Ende ** ** ** */

	pthread_join(thread_command, NULL);

	/* Resourcen freigeben usw... */

	return 0;
}
